Skip to content

Performance: Cache config#276

Open
cllns wants to merge 7 commits into
mainfrom
performance/cache-config
Open

Performance: Cache config#276
cllns wants to merge 7 commits into
mainfrom
performance/cache-config

Conversation

@cllns
Copy link
Copy Markdown
Member

@cllns cllns commented May 5, 2026

~28% fewer allocations (138 -> 99) per render
~18% fewer bytes used (8,739 -> 7,160) per render

+36.8% iteration speed (30,891 ips -> 42,266 ips, 32.4μs -> 23.7μs per render)

(Updated these numbers. The early improvements weren't as high but I closed some apps 😇 and these seem to be consistent)

Using benchmarks/view_benchmark.rb on Ruby 4 (on an M1 Pro).

I originally had this with a class-level self.cached_config which isn't necessary so I removed it. This means each instantiation of a view has to compute cached_config for itself, adding some overhead. Since we've built this library based on the assumption that a single View will be re-used (rather than created new each time it's used), I think that's fine. Open to other perspectives though!

Another option is using reader: true for all of the settings. I experimented with this too, but it's not as clean, because the settings are set on the View class rather than the config, so view_class has to be passed around everywhere, which hides the intent of sending just config settings around. It's got basically the same performance as this approach though.

cllns added 3 commits May 4, 2026 23:20
~22% fewer allocations (161 -> 125)
~14% fewer bytes used (9,936 -> 8,496)

+18.7% iteration speed (23,114 ips -> 27,438 ips, 43.3μs -> 36.5μs per render)

Using benchmarks/view_benchmark.rb on Ruby 4 (on an M1 Pro)
@timriley
Copy link
Copy Markdown
Member

timriley commented May 5, 2026

Thanks for looking into this stuff, @cllns! My question is whether we might actually be able to solve this at a Dry Configurable level, rather than create a workaround for it here? We use Dry Configurable in many other places, so a solve there once would benefit us more.

@cllns
Copy link
Copy Markdown
Member Author

cllns commented May 6, 2026

@timriley I was already working on it :) Just had to figure out the right API. See: dry-rb/dry-configurable#167 and updated changes here.

I made a few tiny refactors to move things into private visibility. There's more I want to do, but I'll do that as a follow-on, so this PR doesn't get too complicated.

@cllns
Copy link
Copy Markdown
Member Author

cllns commented May 13, 2026

Switched the Gemfile to use dry-configurable from 'main' since dry-rb/dry-configurable#167 has been merged

Copy link
Copy Markdown
Member

@timriley timriley left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good, @cllns! Left a few thoughts for you, nothing major. Would you mind taking care of the adjustments?


private

attr_reader :config_data, :prefixes
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a note on why these were public before: I've never really cared about leaving public attr readers on objects, even if they're not formally "public API". It's Ruby, so if anyone really wanted to get to some object, there's always a way they can do it, so I never really tried to fight it.

This is not to say I'm opposed to this change, but I thought I'd just share some of the rationale I held when putting these things together in the first place :)

Happy for this change to go in as part of this PR, anyway :)

# @since 2.3.0
attr_reader :part_class, :part_namespace, :scope_class, :scope_namespace

# Stable identity for the underlying config snapshot, suitable as a cache-key component.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# Stable identity for the underlying config snapshot, suitable as a cache-key component.
# Stable identity for the underlying config snapshot

Since this is already called cache_key, we probably don't need to repeat ourselves in the description :)

Comment on lines +22 to +23
# @api private
# @since 2.3.0
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's stop putting @since tags on new @api private methods. These are for us to use internally only, and putting a @since IMO implies some level of stability to external readers of the code that I don't want to promise. (It's private API, after all!)

I need to make a post about this, but in the meantime I'm trying to stop it proliferating, and removing the tags whenever I edit a file.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Also, for any "since" that is legitimately needed here — now for "@api public" methods only! — it should be @since 3.0.0)

... though I think most of your changes are to private API, AFAICT

Comment on lines +72 to +75

private

attr_reader :renderer
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean, this is probably why I don't like private attr_readers. As a maintainer looking at this class, I see a bunch at the top of the file, and then we have this random one hanging out all the way at the bottom. It's disjointed and it's hard to get a good overview of the class this way.

Maybe we could just leave them at the top, since attr privacy is not really the purpose of this PR?

Comment thread lib/hanami/view.rb
output = rendering.template(
self.class.layout_path(layout),
rendering.scope(config.scope, layout_locals(locals))
File.join(*[config_data.layouts_dir, layout].compact),
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we leave the layout_path method back as it was? I think it makes this code here (which is already doing a lot!) easier to follow. One fewer piece of detailed implementation inlined into the method.

Comment thread lib/hanami/view.rb
Comment on lines +623 to +624
#
# @since 2.3.0
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#
# @since 2.3.0

Another since we can drop. This is not just "@api private", it's fully private :)

Comment thread Gemfile
Comment on lines +39 to +40
gem "stackprof", platform: :mri
gem "memory_profiler"
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these not strictly needed for this PR?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants